import Between from "../phaser/math/Between.js";
import { greaterOrEqual, lessOrEqual } from "../utils/Compare";
import Line from "./Line";
import Point from "./Point";
const EPSILON = 1e-6;

export default class Rect {
    readonly x: number
    readonly y: number
    readonly x_max: number
    readonly y_max: number
    readonly width: number
    readonly height: number
    readonly halfWidth: number
    readonly halfHeight: number
    get x_center() {
        return this.x + this.halfWidth;
    }
    get y_center() {
        return this.y + this.halfHeight;
    }

    static createByPointRandom(pointA: Point, pointB: Point): Rect {
        return new Rect(Math.min(pointA.x, pointB.x), Math.min(pointA.y, pointB.y), Math.abs(pointA.x - pointB.x), Math.abs(pointA.y - pointB.y))
    }

    static createByCenter(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x - (width / 2), y - (height / 2), width, height)
    }

    static createByCenterWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByCenter(point.x, point.y, width, height)
    }

    static createByRightTop(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x - width, y - height, width, height)
    }

    static createByRightTopWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByRightTop(point.x, point.y, width, height)
    }

    static createByLeftTop(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x, y - height, width, height)
    }

    static createByLeftTopWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByLeftTop(point.x, point.y, width, height)
    }

    static createByRightBottom(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x - width, y, width, height)
    }

    static createByRightBottomWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByRightBottom(point.x, point.y, width, height)
    }

    static createByLeftBottom(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x, y, width, height)
    }

    static createByLeftBottomWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByLeftBottom(point.x, point.y, width, height)
    }

    static createByRightCenter(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x - width, y - (height / 2), width, height)
    }

    static createByRightCenterWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByRightCenter(point.x, point.y, width, height)
    }

    static createByLeftCenter(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x, y - (height / 2), width, height)
    }

    static createByLeftCenterWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByLeftCenter(point.x, point.y, width, height)
    }

    static createByTopCenter(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x - (width / 2), y - height, width, height)
    }

    static createByTopCenterWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByTopCenter(point.x, point.y, width, height)
    }

    static createByBottomCenter(x: number, y: number, width: number, height: number): Rect {
        return new Rect(x - (width / 2), y, width, height)
    }

    static createByBottomCenterWithPoint(point: Point, width: number, height: number): Rect {
        return Rect.createByBottomCenter(point.x, point.y, width, height)
    }

    private constructor(x: number, y: number, width: number, height: number) {
        this.x = x;
        this.y = y;
        this.x_max = x + width;
        this.y_max = y + height;
        this.width = width;
        this.height = height;
        this.halfWidth = width / 2;
        this.halfHeight = height / 2;
    }

    getLeftTopPoint() {
        return new Point(this.x, this.y_max);
    }

    getRightTopPoint() {
        return new Point(this.x_max, this.y_max);
    }

    getLeftBottomPoint() {
        return new Point(this.x, this.y);
    }

    getRightBottomPoint() {
        return new Point(this.x, this.y_max);
    }

    // centerLeft
    getCenterLeftPoint() {
        return new Point(this.x, this.y_center);
    }

    // centerRight
    getCenterRightPoint() {
        return new Point(this.x_max, this.y_center);
    }

    // centerTop
    getCenterTopPoint() {
        return new Point(this.x_center, this.y_max);
    }

    // centerBottom
    getCenterBottomPoint() {
        return new Point(this.x_center, this.y);
    }

    getCenterPoint() {
        return new Point(this.x_center, this.y_center);
    }

    minSide() {
        return Math.min(this.width, this.height);
    }

    maxSide() {
        return Math.max(this.width, this.height);
    }

    getXRatio(ratio: number) {
        if (ratio >= 0 && ratio <= 1) {
            return this.x + ratio * this.width;
        }
        return this.x;
    }

    getYRatio(ratio: number) {
        if (ratio >= 0 && ratio <= 1) {
            return this.y + ratio * this.height;
        }
        return this.y;
    }

    getPointRatio(xRatio: number, yRatio: number) {
        return new Point(this.getXRatio(xRatio), this.getYRatio(yRatio));
    }

    getWidthRatio(ratio: number) {
        if (ratio >= 0 && ratio <= 1) {
            return ratio * this.width;
        }
        return this.width;
    }

    getHeightRatio(ratio: number) {
        if (ratio >= 0 && ratio <= 1) {
            return ratio * this.height
        }
        return this.height
    }

    getCenterRectRatio(widthRatio: number, heightRatio: number) {
        const x = this.getXRatio(0.5)
        const y = this.getYRatio(0.5)
        const width = this.getWidthRatio(widthRatio)
        const height = this.getHeightRatio(heightRatio)
        return Rect.createByCenter(x, y, width, height)
    }

    getRectRatio(xRatio: number, yRatio: number, widthRatio: number, heightRatio: number) {
        const x = this.getXRatio(xRatio)
        const y = this.getYRatio(yRatio)
        const width = this.getWidthRatio(widthRatio)
        const height = this.getHeightRatio(heightRatio)
        return new Rect(x, y, width, height)
    }

    intersectLineRay(origin: Line, isInclude = true): Point[] {
        const sectorLines = this.getLine()
        const intersect: Map<String, Point> = new Map()
        for (let index = 0; index < sectorLines.length; index++) {
            const element = sectorLines[index];
            const point = origin.getIntersectPoint(element)
            if (point) {
                if (isInclude) {
                    if (this.containsPoint(point.x, point.y)) {
                        intersect.set(point.x + '-' + point.y, point)
                    }
                } else {
                    intersect.set(point.x + '-' + point.y, point)
                }
            }
        }
        const result: Point[] = []
        intersect.forEach((v1, v2, set) => {
            result.push(v1)
        })
        return result
    }

    getIntersectLine(line: Line): Line | null {
        const clipT = (num: number, denom: number, c: Point) => {
            const tE = c.x;
            const tL = c.y;
            if (Math.abs(denom) < EPSILON) return num < 0;
            const t = num / denom;
            if (denom > 0) {
                if (t > tL) return false;
                if (t > tE) c.x = t;
            } else {
                if (t < tE) return false;
                if (t < tL) c.y = t;
            }
            return true;
        }
        const x1 = line.origin.x;
        const y1 = line.origin.y;
        const x2 = line.target.x;
        const y2 = line.target.y;
        const dx = x2 - x1;
        const dy = y2 - y1;

        if (
            Math.abs(dx) < EPSILON &&
            Math.abs(dy) < EPSILON &&
            x1 >= this.x &&
            x1 <= this.x_max &&
            y1 >= this.y &&
            y1 <= this.y_max
        ) {
            return null;
        }

        const c: Point = new Point(0, 1);
        if (
            clipT(this.x - x1, dx, c) &&
            clipT(x1 - this.x_max, -dx, c) &&
            clipT(this.y - y1, dy, c) &&
            clipT(y1 - this.y_max, -dy, c)
        ) {
            const tE = c.x;
            const tL = c.y;
            const result = Line.create(x1, y1, x2, y2)
            if (tL < 1) {
                result.target.x = x1 + tL * dx;
                result.target.y = y1 + tL * dy;
            }
            if (tE > 0) {
                result.origin.x += tE * dx;
                result.origin.y += tE * dy;
            }
            return result;
        }
        return null;
    }

    isIntersectRect(rect: Rect) {
        return this.x < rect.x_max && this.x_max > rect.x && this.y < rect.y_max && this.y_max > rect.y;
    }

    getIntersectRect(rect: Rect): Rect | null {
        if (this.isIntersectRect(rect)) {
            this.x < rect.x_max && this.x_max > rect.x && this.y < rect.y_max && this.y_max > rect.y;
            const x = Math.max(this.x, rect.x);
            const y = Math.max(this.y, rect.y);
            const width = Math.min(this.x_max, rect.x_max) - x;
            const height = Math.min(this.y_max, rect.y_max) - y;
            return new Rect(x, y, width, height)
        }
        return null
    }

    mergeRect(rect: Rect): Rect {
        const x_min = Math.min(this.x, rect.x);
        const y_min = Math.min(this.y, rect.y);
        const x_max = Math.max(this.x_max, rect.x_max);
        const y_max = Math.max(this.x_max, rect.x_max);
        return new Rect(x_min, y_min, x_max - x_min, y_max - y_min);
    }

    getRectOffset(leftOffset: number, topOffset: number, rightOffset: number, bottomOffset: number) {
        return Rect.createByPointRandom(new Point(this.x + leftOffset, this.y + topOffset), new Point(this.x_max + rightOffset, this.y_max + bottomOffset))
    }

    containsPoint(x: number, y: number, offsetX: number = 0, offsetY: number = 0) {
        return greaterOrEqual(x, this.x + offsetX) && lessOrEqual(x, this.x_max - offsetX) && greaterOrEqual(y, this.y + offsetY) && lessOrEqual(y, this.y_max - offsetY)
    }

    containsPointWithoutBorders(x: number, y: number, offsetX: number = 0, offsetY: number = 0) {
        return x > this.x + offsetX && x < this.x_max - offsetX && y > this.y + offsetY && y < this.y_max - offsetY
    }

    containsLine(startX: number, startY: number, endX: number, endY: number) {
        return this.containsPoint(startX, startY) && this.containsPoint(endX, endY)
    }

    containsRect(x: number, y: number, width: number, height: number) {
        return this.containsPoint(x, y) && this.containsPoint(x + width, y + height);
    }

    getLeftLine() {
        return Line.create(this.x, this.y, this.x, this.y_max);
    }

    getTopLine() {
        return Line.create(this.x, this.y_max, this.x_max, this.y_max);
    }

    getRightLine() {
        return Line.create(this.x_max, this.y_max, this.x_max, this.y);
    }

    getBottomLine() {
        return Line.create(this.x_max, this.y, this.x, this.y);
    }

    getLine() {
        return [this.getTopLine(), this.getRightLine(), this.getBottomLine(), this.getLeftLine(),]
    }

    getRandomPoint() {
        return new Point(this.x + Between(0, this.width), this.y + Between(0, this.height))
    }

    /**
     * 转为顺时针数组
     */
    toClockWiseArray(): number[][] {
        return [[this.x, this.y], [this.x, this.y_max], [this.x_max, this.y_max], [this.x_max, this.y]]
    }

    /**
     * 转为逆时针数组
     */
    toAntiClockWiseArray(): number[][] {
        return [[this.x, this.y], [this.x_max, this.y], [this.x_max, this.y_max], [this.x, this.y_max]]
    }

    toString() {
        return `Rect[${this.x}-${this.y} : ${this.width}-${this.height}]`
    }
}